library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──
## ✔ ggplot2 3.3.6 ✔ purrr 0.3.4
## ✔ tibble 3.1.7 ✔ dplyr 1.0.9
## ✔ tidyr 1.2.0 ✔ stringr 1.4.0
## ✔ readr 2.1.2 ✔ forcats 0.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
library(gganimate)
library(ggtext)
library(lubridate)
##
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
##
## date, intersect, setdiff, union
Statcounter provides mobile vendor market share back to 2010. The original video goes back to the 1990s. The data can be downloaded in CSV format via https://gs.statcounter.com/vendor-market-share/mobile/worldwide/#monthly-201003-202205 After the download, place the CSV file in your project directory.
filename <- "vendor-ww-monthly-201003-202205.csv"
df_raw <- read_csv(filename)
## Rows: 147 Columns: 70
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (1): Date
## dbl (69): Samsung, Apple, Unknown, Nokia, Huawei, Xiaomi, LG, Oppo, Sony, Mo...
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Transform the data to long format for our next steps.
threshold_for_lumping <- 3.1
df_long <- df_raw %>%
pivot_longer(cols = -Date, names_to = "vendor", values_to = "market_share") %>%
# group vendors with smaller market shares to "Other" based on monthly shares
mutate(vendor2 = ifelse(market_share < threshold_for_lumping |
vendor == "Unknown", "Other", vendor),
date = ym(Date)) %>%
filter(date > as_date("2010-03-01")) %>%
count(date, vendor2, wt = market_share, name = "market_share")
The first few rows of the transformed dataframe:
head(df_long)
## # A tibble: 6 × 3
## date vendor2 market_share
## <date> <chr> <dbl>
## 1 2010-04-01 Apple 33.2
## 2 2010-04-01 Nokia 37.6
## 3 2010-04-01 Other 5.11
## 4 2010-04-01 RIM 15.9
## 5 2010-04-01 Sony 8.3
## 6 2010-05-01 Apple 33.1
All grouped vendors:
unique(df_long$vendor2)
## [1] "Apple" "Nokia" "Other" "RIM" "Sony" "Samsung" "HTC"
## [8] "LG" "Huawei" "Lenovo" "Xiaomi" "Oppo" "Mobicel" "Vivo"
df_long %>%
filter(date <= as_date("2011-03-01")) %>%
ggplot(aes(vendor2, market_share)) +
geom_col() +
coord_flip() +
facet_wrap(vars(date))
{ggpubr} is great for creating several chart types, include donut charts. However, for our animation we will build the plot from scratch in {ggplot2}.
p <- df_long %>%
filter(date == as_date("2022-05-01")) %>%
arrange(date, market_share) %>%
mutate(label_pos = cumsum(market_share) / sum(market_share)
- 0.5 * market_share / sum(market_share),
label = sprintf("%s<br>%s %%", vendor2, market_share),
# label = fct_reorder(label, -market_share),
vendor2 = fct_reorder(vendor2, -market_share)) %>%
ggplot(aes(x = 1, market_share, group = vendor2)) +
geom_col(aes(fill = vendor2), position = "fill") +
geom_richtext(aes(x = 1.5, label = label, y = label_pos)) +
paletteer::scale_fill_paletteer_d("palettetown::dratini") +
coord_polar(theta = "y") +
guides(fill = "none") +
theme_void()
p
We just simply add a white circle on top of the pie chart. Voilà , a donut chart. Adjust donut_hole_width to increase or decrease the donut bar. A value of 0 will result in a pie chart, a value of 1.5 or greater will cover the whole pie chart.
donut_hole_width <- 0.75
p_donut <- p +
annotate("rect", xmin = 0, xmax = donut_hole_width, ymin = -Inf, ymax = Inf,
fill = "white") +
geom_text(aes(x = 0, y = 0, label = format(date, "%B\n%Y")), stat = "unique",
size = 8)
p_donut
p_donut <-
df_long %>%
# filter(date == as_date("2022-05-01")) %>%
mutate(vendor2 = factor(vendor2, levels = unique(df_long$vendor2))) %>%
# arrange(date, vendor2) %>%
# now we need to calculate the label position within each month
group_by(date) %>%
arrange(desc(vendor2), .by_group = TRUE) %>%
mutate(label_pos = cumsum(market_share) / sum(market_share)
- 0.5 * market_share / sum(market_share),
label = sprintf("%s\n%s %%", vendor2,
scales::number(market_share, accuracy = 0.1)),
label = fct_reorder(label, market_share)
# vendor2 = fct_reorder(vendor2, -market_share)
) %>%
ungroup() %>%
ggplot(aes(x = 1, market_share, group = vendor2)) +
geom_col(aes(fill = vendor2), position = "fill") +
ggrepel::geom_text_repel(aes(x = 1.5, label = label, y = label_pos),
hjust = 0, family = "Fira Sans", segment.size = 0.3,
min.segment.length = 0, nudge_x = 0.3, point.padding = 1e-05,
label.padding = 0.3, color = "white") +
# paletteer::scale_fill_paletteer_d("palettetown::dratini", direction = -1) +
# paletteer::scale_fill_paletteer_d("ggthemes::Jewel_Bright") +
paletteer::scale_fill_paletteer_d("palettetown::lapras") +
# paletteer::scale_color_paletteer_d("palettetown::dratini") +
coord_polar(theta = "y") +
guides(fill = "none", color = "none") +
# semi-transparent ring
annotate("rect", xmin = 0, xmax = donut_hole_width + 0.15, ymin = -Inf, ymax = Inf,
fill = alpha("grey4", 0.25)) +
# inner ring
annotate("rect", xmin = 0, xmax = donut_hole_width, ymin = -Inf, ymax = Inf,
fill = "grey4") +
# geom_text(aes(x = 0, y = 0, label = format(date, "%B\n%Y")), stat = "unique",
# size = 8, family = "Fira Sans SemiBold", color = "white")
geom_richtext(
aes(x = 0, y = 0,
label = sprintf(
"<span style='color: grey80'>%s</span><br><span style='font-size: 40pt'>%s</span>",
format(date, "%B"), year(date))),
stat = "unique", size = 8, family = "Fira Sans SemiBold", color = "white",
fill = NA, label.size = 0, lineheight = 1.67) +
theme_void(base_family = "Fira Sans") +
theme(
plot.background = element_rect(color = NA, fill = "grey4"),
plot.margin = margin(10, 10, 10, 10)
)
## Warning: Ignoring unknown parameters: label.padding
p_anim <- p_donut +
transition_states(date)
anim <- animate(p_anim, res = 100, width = 600, height = 600, fps = 12,
duration = 60)
anim_save("animated-donut-chart.gif", anim)